package com.linyu.mallchat.common.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.linyu.mallchat.common.user.dao.UserDao;
import com.linyu.mallchat.common.user.domain.entity.User;
import com.linyu.mallchat.common.user.service.LoginService;
import com.linyu.mallchat.common.user.service.impl.LoginServiceImpl;
import com.linyu.mallchat.common.websocket.domain.dto.WSChannelExtraDTO;
import com.linyu.mallchat.common.websocket.domain.vo.resp.WSBaseResponse;
import com.linyu.mallchat.common.websocket.service.WebSocketService;
import com.linyu.mallchat.common.websocket.service.adapter.WebSocketAdapter;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 专门管理 WebSocket 逻辑，包括推拉
 */
@Service
public class WebSocketServiceImpl implements WebSocketService {

    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private LoginService loginService;

    /**
     * 管理所有在线用户的链接(登录态/游客)
     */
    private static final ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();


    public static final Duration DURATION = Duration.ofHours(1);
    public static final int MAXIMUM_SIZE = 10000;
    /**
     * 临时保存登录 code 和 channel 的映射关系
     */
    private static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(DURATION)
            .build();
    @Autowired
    private UserDao userDao;

    /**
     * 保存 ws 链接
     * @param channel
     */
    @Override
    public void connect(Channel channel) {
        // 不知道是否登录认证了，这里就直接设置一个空对象保存链接
        ONLINE_WS_MAP.put(channel, new WSChannelExtraDTO());
    }

    /**
     * 授权登录
     * @param channel 哪个链接要申请这个认证
     */
    @Override
    public void handleLoginReq(Channel channel) {
        // 生成随机码
        Integer code = generateLoginCode(channel);
        // 找微信申请带参二维码
        try {
            WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
            // 把二维码推送给前端
            sendMsg(channel, WebSocketAdapter.buildResp(wxMpQrCodeTicket));
        } catch (WxErrorException e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 用户下线，踢出缓存
     * @param channel 连接
     */
    @Override
    public void remove(Channel channel) {
        ONLINE_WS_MAP.remove(channel);
        // todo 用户下线

    }

    /**
     * 扫码用户登录成功通知,清除本地Cache中的loginCode和channel的关系
     * @param loginCode
     * @param uid
     * @return
     */
    @Override
    public void scanLoginSuccess(Integer loginCode, Long uid) {
        // 确认连接在机器上
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode);
        if (Objects.isNull(channel)){
            return;
        }
        User user = userDao.getById(uid);
        // 移除 code
        WAIT_LOGIN_MAP.invalidate(loginCode);
        // 调用登录模块获取 token
        String token = loginService.login(uid);
        // 用户登录
        sendMsg(channel, WebSocketAdapter.buildResp(user, token));

    }

    // 把二维码推送给前端
    private void sendMsg(Channel channel, WSBaseResponse<?> resp) {
        // 推送给前端信息
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(resp)));
    }

    /**
     * 生成随机码
     * @param channel
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        do{
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        }while(Objects.nonNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code, channel))); // 这个 Map 方法如果有这个就返回，如果没有就返回 null
        return code;
    }


}
